package im.eg.spring;

import cn.hutool.core.util.StrUtil;
import im.eg.spring.anno.*;
import im.eg.spring.itfs.BeanNameAware;
import im.eg.spring.itfs.BeanPostProcessor;
import im.eg.spring.itfs.InitializingBean;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class EgApplicationContext {

    private final ConcurrentHashMap<String, Object> BEAN_MAP = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, BeanDefinition> BEAN_DEFINITION_MAP = new ConcurrentHashMap<>();
    private final List<BeanPostProcessor> beanPostProcessorList = new LinkedList<>();

    public EgApplicationContext(Class<?> appConfig) {
        //组件扫描
        scanComponents(appConfig);

        //首先创建BeanPostProcessor组件
        for (String beanName : BEAN_DEFINITION_MAP.keySet()) {
            BeanDefinition beanDefinition = BEAN_DEFINITION_MAP.get(beanName);

            //默认BeanPostProcessor实现类组件为单例、非懒加载（多例无意义），进行组件创建
            if (BeanPostProcessor.class.isAssignableFrom(beanDefinition.getClazz())) {
                if (BEAN_MAP.containsKey(beanName)) {
                    throw new RuntimeException("已存在同名bean");
                }
                Object beanPostProcessor = createBean(beanName, beanDefinition.getClazz());
                BEAN_MAP.put(beanName, beanPostProcessor);
                beanPostProcessorList.add((BeanPostProcessor) beanPostProcessor);
            }
        }

        //再创建非BeanPostProcessor组件
        for (String beanName : BEAN_DEFINITION_MAP.keySet()) {
            BeanDefinition beanDefinition = BEAN_DEFINITION_MAP.get(beanName);

            if (!BeanPostProcessor.class.isAssignableFrom(beanDefinition.getClazz())) {
                String scope = beanDefinition.getScope();
                switch (scope) {
                    case "singleton":
                        if (!beanDefinition.isLazy()) {
                            if (BEAN_MAP.containsKey(beanName)) {
                                throw new RuntimeException("已存在同名bean");
                            }
                            BEAN_MAP.put(beanName, createBean(beanName, beanDefinition.getClazz()));
                        }
                        break;
                    case "prototype":
                        break;
                    default:
                        throw new RuntimeException("未知scope");
                }
            }
        }

    }

    private Object createBean(String beanName, Class<?> clazz) {
        try {
            //实例化
            Object bean = clazz.newInstance();

            //依赖注入
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    boolean required = field.getAnnotation(Autowired.class).required();
                    Object autowiredBean = getBean(field.getName());
                    if (required && autowiredBean == null) {
                        throw new RuntimeException("bean不存在，注入失败");
                    }

                    field.setAccessible(true);
                    field.set(bean, autowiredBean);
                }
            }

            //BeanNameAware回调接口，将beanName返回给开发人员
            if (bean instanceof BeanNameAware) {
                ((BeanNameAware) bean).setBeanName(beanName);
            }

            //初始化前
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                bean = beanPostProcessor.postProcessBeforeInitialization(bean, beanName);
            }

            //初始化（提供给开发者）
            if (bean instanceof InitializingBean) {
                try {
                    ((InitializingBean) bean).afterPropertiesSet();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            //初始化后
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                bean = beanPostProcessor.postProcessAfterInitialization(bean, beanName);
            }

            return bean;
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private void scanComponents(Class<?> appConfig) {
        if (appConfig.isAnnotationPresent(ComponentScan.class)) {
            //获取扫描路径
            ComponentScan componentScan = appConfig.getAnnotation(ComponentScan.class);
            String scanPath = componentScan.value();
            if (StrUtil.isBlank(scanPath)) {
                throw new RuntimeException("组件扫描路径不能为空");
            }

            //扫描组件
            URL resource = EgApplicationContext.class.getClassLoader().getResource(scanPath.replaceAll("\\.",
                    System.getProperty("os.name").toLowerCase().contains("win") ? "\\\\" : "/"));
            File componentPath = null;
            try {
                if (resource == null) {
                    throw new RuntimeException("resource为空");
                }
                componentPath = new File(resource.toURI());
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
            scanDirectory(scanPath, componentPath);
        }
    }

    /**
     * 扫描组件
     *
     * @param scanPath      扫描路径，比如 im.eg.service
     * @param componentPath 组件路径，该路径表示一个文件夹，绝对路径
     */
    private void scanDirectory(String scanPath, File componentPath) {
        if (componentPath.isDirectory()) {
            File[] files = componentPath.listFiles();
            assert files != null;
            for (File file : files) {
                //扫描到class文件
                if (file.isFile() && file.getAbsolutePath().endsWith(".class")) {
                    try {
                        int idxOfLastSeparator = file.getAbsolutePath().lastIndexOf(File.separator);
                        if (idxOfLastSeparator != -1) {
                            // +1 表示从分隔符后进行截取
                            String classFileName = file.getAbsolutePath().substring(idxOfLastSeparator + 1);
                            String className = classFileName.replaceAll(".class", "");
                            Class<?> clazz = EgApplicationContext.class.getClassLoader().loadClass(scanPath + "." + className);

                            //将组件存放到map<beanName,beanDefinition>中
                            if (clazz.isAnnotationPresent(Component.class)) {
                                Component componentAnno = clazz.getAnnotation(Component.class);
                                String beanName = componentAnno.value();

                                if (StrUtil.isBlank(beanName)) {
                                    throw new RuntimeException("请指定beanName");
                                }

                                String scopeVal = "singleton";
                                if (clazz.isAnnotationPresent(Scope.class)) {
                                    scopeVal = clazz.getAnnotation(Scope.class).value();
                                }

                                boolean lazyLoad = false;
                                if (clazz.isAnnotationPresent(Lazy.class)) {
                                    lazyLoad = clazz.getAnnotation(Lazy.class).value();
                                }

                                BeanDefinition beanDefinition = new BeanDefinition(clazz, scopeVal, lazyLoad);
                                if (BEAN_DEFINITION_MAP.get(beanName) != null) {
                                    throw new RuntimeException("存在同名bean");
                                }
                                BEAN_DEFINITION_MAP.put(beanName, beanDefinition);
                            }
                        }
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
                //扫描到目录，则继续向下扫描
                else if (file.isDirectory()) {
                    int idx = file.getAbsolutePath().lastIndexOf(File.separator);
                    scanDirectory(scanPath + "." + file.getAbsolutePath().substring(idx + 1), file);
                }
            }
        }
    }

    /**
     * 根据beanName获取bean
     *
     * @param beanName
     * @return null表示不存在
     */
    public Object getBean(String beanName) {
        if (StrUtil.isBlank(beanName)) {
            throw new RuntimeException("beanName不能为空");
        }

        BeanDefinition beanDefinition = BEAN_DEFINITION_MAP.get(beanName);
        if (beanDefinition == null) {
            return null;
        }

        String scope = beanDefinition.getScope();
        if (StrUtil.isBlank(scope)) {
            throw new RuntimeException("scope值为空");
        }
        switch (scope) {
            case "singleton":
                if (beanDefinition.isLazy()) {
                    //已加载
                    if (BEAN_MAP.containsKey(beanName)) {
                        return BEAN_MAP.get(beanName);
                    }

                    //未加载
                    Object bean = createBean(beanName, beanDefinition.getClazz());
                    if (BEAN_MAP.containsKey(beanName)) {
                        throw new RuntimeException("存在同名bean");
                    }
                    BEAN_MAP.put(beanName, bean);
                    return bean;
                } else {
                    return BEAN_MAP.get(beanName);
                }
            case "prototype":
                return createBean(beanName, beanDefinition.getClazz());
            default:
                throw new RuntimeException("未知scope");
        }
    }

}
